iT邦幫忙

2023 iThome 鐵人賽

DAY 5
0

上篇簡單講述了閉包的特性之後,今天來點不同的舉例來加深印象!

先來看看以下程式碼:

function createFunctionArray() {
  var functionArray = []; // 創建一個函式陣列,用於存放多個函式

  for (var i = 0; i < 5; i++) {
    // 在迴圈中,我們創建一個匿名函式,該函式將輸出目前的索引值
    functionArray.push(
      function printIndex() {
        console.log(i);
      }
    );
  }

  return functionArray;
}

var functions = createFunctionArray(); // 創建一個包含多個函式的陣列

functions[0](); // 呼叫陣列中的第一個函式,將輸出 5
functions[1](); // 呼叫陣列中的第二個函式,將輸出 5

是不是會很好奇,為什麼輸出都是 5

回想一下閉包的特性,雖然上面的程式碼似乎應該依次輸出 01
但實際上它們都輸出 5 的原因是因為在迴圈中創建的匿名函式捕獲了變數 i 的引用,而不是其值。

createFunctionArray 函式中,我們創建了一個函式陣列 functionArray
並使用一個迴圈來添加匿名函式到這個陣列中。這些匿名函式都捕獲了外部作用域的變數 i

當迴圈完成並退出後,i 的值等於 5,因為這是使迴圈停止的條件。
由於這些匿名函式仍然引用相同的 i,當呼叫這些函式時,都將使用當前的 i 值,即 5
這就是為什麼無論呼叫 functions[0]() 還是 functions[1](),都輸出 5 的原因。


那麼要如何讓上面的輸出結果是 01呢?

我們可以在每次迴圈迭代時創建一個新的作用域,這樣每個匿名函式都會捕獲不同的 i 值。

  • 方式一:使用 let 來產生區塊作用域
function createFunctionArray() {
  var functionArray = [];

  for (let i = 0; i < 5; i++) { // 使用 let 創建區塊作用域
    functionArray.push(function() {
      console.log(i);
    });
  }

  return functionArray;
}

var functions = createFunctionArray();

functions[0](); // 輸出 0
functions[1](); // 輸出 1

使用 let 創建的 i 變數在每次迴圈迭代時都有自己的區塊作用域,
因此每個匿名函式都能正確地捕獲到其自己的 i 值。

  • 方式二:IIFE (Immediately Invoked Function Expression)(立即呼叫函式表示法)
function createFunctionArray() {
  var functionArray = [];

  for (var i = 0; i < 5; i++) {
    (function (index) { // 使用IIFE創建新的作用域
      functionArray.push(function() {
        console.log(index);
      });
    })(i); // 將i作為參數傳遞給IIFE
  }

  return functionArray;
}

var functions = createFunctionArray();

functions[0](); // 輸出 0
functions[1](); // 輸出 1

這樣做之後,每個匿名函式都捕獲了它自己的 index 值,使得輸出正確。

IIFE 通常用於

  • 創建私有作用域
    IIFE 創建了一個獨立的作用域,其中的變數在函式執行後會被銷毀。
    這有助於防止變數污染全域作用域。
  • 模組化程式碼
    IIFE 可用於創建模組化的程式碼區塊,其中可以定義私有變數和函式,
    並通過返回公共接口來封裝它們,以供外部使用。

閉包實際應用

  1. 封裝私有變數和函式:
    閉包可以用來創建具有私有變數的函式,以增加安全性。

    function createCounter() {
      let count = 0;
      return {
        increment: function () {
          count++;
        },
        getCount: function () {
          return count;
        }
      };
    }
    
    const counter = createCounter();
    counter.increment();
    console.log(counter.getCount()); // 輸出 1
    
  2. 事件處理程序:
    閉包可用於處理事件。當使用者點擊按鈕時,可以使用閉包來記錄點擊次數。

    const button = document.getElementById('likeButton');
    let clickCount = 0;
    
    button.addEventListener('click', function () {
      clickCount++;
      console.log(`點擊次數:${clickCount}`);
    });
    
  3. setTimeout 和 setInterval:
    使用閉包可以創建具有狀態的定時任務。

    function createTimer() {
      let seconds = 0;
      return function () {
        seconds++;
        console.log(`過去秒數:${seconds}`);
      };
    }
    
    const timer = createTimer();
    setInterval(timer, 1000);
    
  4. 模組模式:
    閉包可用於創建模組,將相關的函式和數據封裝在一起,以提供更好的代碼組織。
    假設我們正在建立一個計數器模組:

    const CounterModule = (function () {
      // 私有變數
      let count = 0;
    
      // 增加
      function increment() {
        count++;
      }
    
      // 減少
      function decrement() {
        count--;
      }
    
      // 獲取當前值
      function getCount() {
        return count;
      }
    
      // 公開的部分
      return {
        increment: increment,
        decrement: decrement,
        getCount: getCount
      };
    })();
    
    // 使用計數器模組
    CounterModule.increment();
    CounterModule.increment();
    console.log(CounterModule.getCount()); // 輸出 2
    CounterModule.decrement();
    console.log(CounterModule.getCount()); // 輸出 1
    

    在這個例子中,CounterModule 是一個使用閉包實現的模組,
    包含了私有變數 count 和三個公開方法:incrementdecrementgetCount
    這樣,我們可以使用模組來管理計數器的狀態,同時保護了 count 變數,
    使其無法被外部直接訪問或修改,提供了更好的代碼組織和隔離。

  5. 快取:
    閉包可用於創建簡單的快取,以避免重複計算。

    function createCache() {
      const cache = {};
      return function (key) {
        if (key in cache) {
          return cache[key];
        } else {
          const result = /* 計算結果 */;
          cache[key] = result;
          return result;
        }
      };
    }
    
    const getValue = createCache();
    console.log(getValue('data')); // 計算並快取結果
    console.log(getValue('data')); // 直接使用快取的結果
    

深入了解不同的例子與閉包實際應用之後,相信大家有對閉包留下深刻的印象!
今天就先分享到這,我們下篇見!


參考資料:

文章同步於個人部落格:Viiisit!(歡迎參觀 ୧ʕ•̀ᴥ•́ʔ୨)


上一篇
Day 04 - 理解 JavaScript ,為什麼要知道閉包(上)?
下一篇
Day 06 - 理解 JavaScript ,為什麼要知道一級函式、回呼函式?
系列文
從零開始,在 coding 路上的 30 個為什麼?不對!是無數個為什麼!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言